# -*- coding: utf-8 -*-

'''
作者：tiandongjun
功能：模拟cpss服务器
版本：
日期：
2019-03-13 1.0：可以运行，接收注册，心跳，图片下载功能
2019-03-19 2.0：添加ctcp协议字段定义，resultcode等，解析json数据，注册认证后MD5校验
2019-03-21 3.0: 增加sendall返回值判断，None是成功，否则异常
2019-03-22 4.0: 增加raise参数（将消息内容抛出）,判断8字节接收数据是否接收成功
2019-03-25 5.0: 封装函数，使用logging模块
2019-04-09 6.0: 重构，找到内存泄漏的地方
2019-04-10 7.0: logging 增加循环写日志
2019-05-10 8.0: 图片目录pics，修改logging打印的内容
2019-06-11 9.0: 增加显示图片功能，增加大图上叠加小图框
2019-09-17 10.0: 增加判断图片格式，check_jpg==0 只检查图片格式，不返回错误
                 增加统计图片数量功能
2019-09-20 11.0: 增加一个写文件线程,一个回复消息的线程
2019-10-14 12.0: logginginfo 增加CTCP，返回图片错误数量，重名图片数量
2019-11-1  13.0: logging部分修改
2020-04-24 13.1: 增加图片接收时间和shottime之间时间差的计算，用 datetime
2020-06-22 13.2: 增加车牌图片
2020-07-24 13.3：增加result_code 初始化 
2020-10-26 13.4: 重构
'''

'''
抓包举例：
注册：
1, ipc -> cpss
00000000  5a 58 43 50 00 00 00 48  14 00 00 00 00 00 00 3c ZXCP...H .......<
00000010  7b 0a 09 22 44 65 76 69  63 65 49 64 22 3a 09 22 {.."Devi ceId":."
00000020  30 30 30 30 30 30 30 30  30 30 31 33 31 30 30 30 00000000 00131000
00000030  30 30 35 32 22 2c 0a 09  22 44 65 76 69 63 65 54 0052",.. "DeviceT
00000040  79 70 65 22 3a 09 22 50  55 22 0a 7d 00 00 00 00 ype":."P U".}....
2, cpss -> ipc
    00000000  5a 58 43 50 00 00 00 92  18 00 00 00 00 00 00 00 ZXCP.... ........
    00000010  00 00 00 82 7b 0a 20 20  20 20 22 72 65 61 6c 6d ....{.     "realm
    00000020  22 3a 20 22 30 30 30 30  30 36 31 32 33 34 35 36 ": "0000 06123456
    00000030  37 38 39 22 2c 0a 20 20  20 20 22 6e 6f 6e 63 65 789",.     "nonce
    00000040  22 3a 20 22 34 63 22 2c  0a 20 20 20 20 22 6f 70 ": "4c", .    "op
    00000050  61 71 75 65 22 3a 20 22  63 66 31 32 33 31 63 33 aque": " cf1231c3
    00000060  34 63 38 34 61 64 63 34  22 2c 0a 20 20 20 20 22 4c84adc4 ",.    "
    00000070  71 6f 70 22 3a 20 22 61  75 74 68 22 2c 0a 20 20 qop": "a uth",.  
    00000080  20 20 22 61 6c 67 6f 72  69 74 68 6d 22 3a 20 22   "algor ithm": "
    00000090  4d 44 35 22 0a 7d 00 00  00 00                   MD5".}.. ..
3, ipc -> cpss
00000050  5a 58 43 50 00 00 01 39  14 01 00 01 00 00 01 2d ZXCP...9 .......-
00000060  7b 0a 09 22 72 65 61 6c  6d 22 3a 09 22 30 30 30 {.."real m":."000
00000070  30 30 36 31 32 33 34 35  36 37 38 39 22 2c 0a 09 00612345 6789",..
00000080  22 6e 6f 6e 63 65 22 3a  09 22 34 63 22 2c 0a 09 "nonce": ."4c",..
00000090  22 6f 70 61 71 75 65 22  3a 09 22 63 66 31 32 33 "opaque" :."cf123
000000A0  31 63 33 34 63 38 34 61  64 63 34 22 2c 0a 09 22 1c34c84a dc4",.."
000000B0  71 6f 70 22 3a 09 22 61  75 74 68 22 2c 0a 09 22 qop":."a uth",.."
000000C0  61 6c 67 6f 72 69 74 68  6d 22 3a 09 22 4d 44 35 algorith m":."MD5
000000D0  22 2c 0a 09 22 75 73 65  72 6e 61 6d 65 22 3a 09 ",.."use rname":.
000000E0  22 30 30 30 30 30 30 30  30 30 30 31 33 31 30 30 "0000000 00013100
000000F0  30 30 30 35 32 22 2c 0a  09 22 75 72 69 22 3a 09 00052",. ."uri":.
00000100  22 63 70 73 73 3a 2f 2f  31 30 2e 39 30 2e 32 36 "cpss:// 10.90.26
00000110  2e 32 33 33 3a 39 39 39  39 22 2c 0a 09 22 63 6e .233:999 9",.."cn
00000120  6f 6e 63 65 22 3a 09 22  63 35 65 64 65 38 63 39 once":." c5ede8c9
00000130  65 38 34 37 35 33 61 65  64 65 34 65 38 36 30 62 e84753ae de4e860b
00000140  33 36 63 30 61 65 38 65  22 2c 0a 09 22 6e 63 22 36c0ae8e ",.."nc"
00000150  3a 09 22 30 30 30 30 30  31 22 2c 0a 09 22 72 65 :."00000 1",.."re
00000160  73 70 6f 6e 73 65 22 3a  09 22 33 66 31 38 34 35 sponse": ."3f1845
00000170  30 31 63 30 61 64 62 65  33 31 39 31 37 61 62 31 01c0adbe 31917ab1
00000180  32 39 37 35 36 34 30 63  38 34 22 0a 7d 00 00 00 2975640c 84".}...
00000190  00                                               .
4, cpss -> ipc
    0000009A  5a 58 43 50 00 00 00 56  18 01 00 01 00 00 00 00 ZXCP...V ........
    000000AA  00 00 00 46 7b 0a 20 20  20 20 22 53 79 73 74 65 ...F{.     "Syste
    000000BA  6d 54 69 6d 65 22 3a 20  22 32 30 31 39 2d 30 33 mTime":  "2019-03
    000000CA  2d 30 39 20 31 33 3a 33  35 3a 30 39 22 2c 0a 20 -09 13:3 5:09",. 
    000000DA  20 20 20 22 4b 65 65 70  41 6c 69 76 65 50 65 72    "Keep AlivePer
    000000EA  69 6f 64 22 3a 20 33 30  0a 7d 00 00 00 00       iod": 30 .}....

心跳：
1, ipc -> cpss
00000191  5a 58 43 50 00 00 00 04  04 02 00 02             ZXCP.... ....
2, cpss -> ipc
    000000F8  5a 58 43 50 00 00 00 08  08 02 00 02 00 00 00 00 ZXCP.... ........

接收图片：
1, ipc -> cpss
000001A9  5a 58 43 50 00 09 04 25  14 08 00 04 00 00 03 16 ZXCP...% ........
000001B9  7b 0a 09 22 46 61 63 65  53 65 74 49 64 22 3a 09 {.."Face SetId":.
000001C9  22 30 30 30 30 30 30 30  30 30 30 31 33 31 30 30 "0000000 00013100
000001D9  30 30 30 35 32 30 32 32  30 31 39 30 33 30 39 31 00052022 01903091
000001E9  33 33 35 33 38 30 30 30  30 31 22 2c 0a 09 22 44 33538000 01",.."D
000001F9  65 76 69 63 65 49 44 22  3a 09 22 30 30 30 30 30 eviceID" :."00000
00000209  30 30 30 30 30 31 33 31  30 30 30 30 30 35 32 22 00000131 0000052"
00000219  2c 0a 09 22 53 68 6f 74  54 69 6d 65 22 3a 09 22 ,.."Shot Time":."
00000229  32 30 31 39 2d 30 33 2d  30 39 20 31 33 3a 33 35 2019-03- 09 13:35
00000239  3a 33 38 2e 36 38 30 22  2c 0a 09 22 50 69 63 46 :38.680" ,.."PicF
00000249  6d 74 22 3a 09 22 6a 70  67 22 2c 0a 09 22 50 69 mt":."jp g",.."Pi
00000259  63 52 65 66 22 3a 09 30  2c 0a 09 22 46 61 63 65 cRef":.0 ,.."Face
00000269  73 22 3a 09 5b 7b 0a 09  09 09 22 46 61 63 65 49 s":.[{.. .."FaceI
00000279  64 53 75 66 66 69 78 22  3a 09 22 30 36 30 30 30 dSuffix" :."06000
00000289  30 32 22 2c 0a 09 09 09  22 50 69 63 46 6d 74 22 02",.... "PicFmt"
00000299  3a 09 22 6a 70 67 22 2c  0a 09 09 09 22 50 69 63 :."jpg", ...."
2, cpss -> ipc
    00000118  5a 58 43 50 00 00 00 08  08 08 00 04 00 00 00 00 ZXCP.... ........

包格式：
pack() {	
  self.pack_start_code	32
  pack_length	32
  self.reserved	3
  self.body_flag	1
  self.msg_type	2
  self.msg_code	10
  self.request_id	16
  if (self.msg_type == MT_RES) {	
     self.result_code	32
  }	
  if (self.body_flag == '1') {	
     pack_body()	
  }	
}	


设计流程：
1，接收数据8个字节，找到pack_start_code('5a 58 43 50 ') + pack_length
2，接收pack_length个数据，解析msg_type,self.msg_code，判断数据消息类型进行数据回复
3，跳到步骤1
'''
import socketserver,time,random,json
import hashlib,os
import logging
from logging.handlers import RotatingFileHandler
from queue import Queue
import threading
import datetime

LOG = 1
noresponse = 0
save_cpssbin = 1
save_jpg = 1
check_jpg = 0
result_code_err = 0
VERSION = '13.4'
registerIPs = []
ip_registerTime = {}
server = 0
_running = True
ctcplogging = logging.getLogger('ctcp')

threadNumsLock = threading.Lock()
allThreads = []
resultPicNum = {} #{'ip:port': [], ...}
server_runtime = [0]

def output_resultcode(intcode):
    '''
    结果码	说明
    0	成功
    1	认证失败
    2	拒绝访问
    3	服务器连接数已达到上限，请尝试其它服务器
    4	不支持的命令
    5	命令格式错误
    6	JSON格式错误
    7	JSON消息中引用的二进制数据不存在
    8	缺少必选的参数
    9	参数取值错误
    '''
    if (intcode >=0 and intcode<10):
        result_code = '0000000'
        result_code += str(intcode)
        ctcplogging.info('result_code=%s'%result_code)
        result_code = bytes.fromhex(result_code)             
    else:
        ctcplogging.info("输入数字不正确，使用00000000 作为result_code")
        result_code = bytes.fromhex('00 00 00 00 ') 
    return result_code


class CTCPLog():
    def __init__(self):
        pass
    
    def info(self, msg):
        try:
            mylogging.info(msg)
        except Exception as e:
            mylogging.error(msg + ' , repeat because of   ' + str(e))
            
    def error(self, msg):
        try:
            mylogging.error(msg)
        except Exception as e:
            mylogging.error(msg + ' , repeat because of   ' + str(e))
    
    def warning(self, msg):
        try:
            mylogging.warning(msg)
        except Exception as e:
            mylogging.error(msg + ' , repeat because of   ' + str(e))
"""
使用自定义类，再次封装 logging 使用方法
1, 修改 ctcplogging = logging.getLogger('ctcp') ==> mylogging = logging.getLogger('ctcp')
2, 修改 init_loggingSetCTCP 中的 ctcplogging ==> mylogging
3, 增加 ctcplogging = CTCPLog()
"""  
    
def init_loggingSetCTCP():
    """
        功能：初始化logging设置，将打印信息分别记录到文件和屏幕上
    :return:
    """
    # 打印到文件
    localtime = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
    logFilename = "./ctcp/log/" + localtime + "_cpss.log"
    logging.basicConfig(level=logging.DEBUG,format='%(asctime)s [line:%(lineno)d] %(filename)s %(levelname)-8s %(message)s') 
    #logging.basicConfig(level=logging.DEBUG,format="%(asctime)s %(filename)s [line:%(lineno)d] --> %(funcName)s %(levelname)-8s %(message)s")
    # datefmt='%a, %d %b %Y %H:%M:%S',
    ctcplogging.handlers = []
    ################################################################################################
    #定义一个RotatingFileHandler，最多备份5个日志文件，每个日志文件最大10M
    Rthandler = RotatingFileHandler(logFilename, maxBytes=50 * 1024 * 1024, backupCount=50)
    Rthandler.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s [line:%(lineno)d] %(filename)s %(levelname)-8s %(message)s') 
    #formatter = logging.Formatter("%(asctime)s %(filename)s [line:%(lineno)d] --> %(funcName)s %(levelname)-8s %(message)s")
    
    Rthandler.setFormatter(formatter)
    ctcplogging.addHandler(Rthandler)

    # 打印到屏幕
    '''
    console = logging.StreamHandler()
    console.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s [line:%(lineno)d] %(filename)s %(levelname)-8s %(message)s') 
    console.setFormatter(formatter)
    ctcplogging.addHandler(console)
    '''
    #print(ctcplogging.handlers)
    #CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET

 
class echorequestserver(socketserver.BaseRequestHandler):    
    def init_params(self):
        self.pack_start_code = bytes.fromhex('5a 58 43 50 ')
        self.client_data = b''
        self.picPacksum = 0 # 图片数据包总数0
        self.Bigpicsum = 0 # 大图总数0
        self.Smallpicsum = 0 # 小图总数0         
        self.BigMotorpicsum = 0 # 大图总数0
        self.SmallMortopicsum = 0 # 小图总数0                                        
        self.errpicsum = 0 # 错误图片数量0
        self.zeropicsum = 0 # 图片大小为0的数量0
        self.repeatpicsum = 0
        self.result_code = bytes.fromhex('00 00 00 00 ') # 返回值
                
    def getThreads(self, threadType = ''):
        threadNumsLock.acquire()   
        try:  
            msg = "CTCP线程(%s)\n"%threadType
            msg += 'before :{}个线程,[{}]'.format(len(allThreads), allThreads)
            aliveThreads=[]
            for curthread in allThreads:
                if curthread.is_alive():
                    aliveThreads.append(curthread)
            allThreads.clear()
            allThreads.extend(aliveThreads)
            if threading.current_thread() not in aliveThreads:
                allThreads.append(threading.current_thread())
            msg += '\nafter :{}个线程,[{}]'.format(len(allThreads),allThreads)
            ctcplogging.info(str(self.client_address)+": " + msg)            
        except Exception as e:
            ctcplogging.error(str(self.client_address)+": " + 'except: ' + str(e))
        if threadNumsLock.locked():
            threadNumsLock.release()     
            
    def read_8bits(self):
        conn = self.request
        client_headdata = b''
        # 逐个字节读取，判断4字节是50 5a 58 43 则再读4个字节作为长度
        try:
            while True:                
                if not _running:
                    conn.close()
                    return False
                recv_data = conn.recv(1) 
                if not recv_data:
                    err_log = '应接收1个字节，实际没有收到数据,退出'
                    err_log += "服务器断开连接"
                    conn.close()
                    ctcplogging.error(str(self.client_address)+": "+ err_log)
                    return False
                if recv_data == bytes.fromhex('5a'):  
                    client_headdata = recv_data                       
                elif recv_data == bytes.fromhex('58'):
                    if client_headdata == bytes.fromhex('5a'):
                        client_headdata += recv_data 
                elif recv_data == bytes.fromhex('43'): 
                    if client_headdata == bytes.fromhex('5a 58'):
                        client_headdata += recv_data 
                elif recv_data == bytes.fromhex('50'):  
                    if client_headdata == bytes.fromhex('5a 58 43'):
                        client_headdata += recv_data
                        recv_data = b''
                        for _ in range(4):
                            recv_data += conn.recv(1) 
                            if not recv_data:
                                err_log = '应接收1个字节，实际没有收到数据,退出'
                                err_log += "服务器断开连接"
                                conn.close()
                                ctcplogging.error(str(self.client_address)+": "+ err_log)
                                return False
                        client_headdata = self.pack_start_code+recv_data
                        return client_headdata
                else:
                    ctcplogging.error(str(self.client_address)+": "+ "接收数据:{},二进制:{}".
                             format(recv_data,''.join(["%02X " % x for x in recv_data]).strip()))   
                    client_headdata = b''                            
            # tcp接收数据,ctcp协议，self.pack_start_code(4 Bytes), pack_length(4 Bytes)
            #client_headdata = conn.recv(8)  
        except Exception as e:        
            err_log = '接收数据,异常: ' + str(e)
            err_log += "服务器断开连接"
            conn.close()
            ctcplogging.error(str(self.client_address)+": "+ err_log)
            return False
        return True
        
    def recv_bytes(self, PACK_SIZE):
        conn = self.request
        recv_data = b''
        try:
            recv_data = conn.recv(PACK_SIZE)
            if not recv_data:
                err_log = '应接收{}个字节，实际没有收到数据,退出'.format(PACK_SIZE)
                err_log += "服务器断开连接"
                conn.close()
                ctcplogging.error(str(self.client_address)+": "+ err_log)
                return False
            rcv_len=len(recv_data)
            while  rcv_len < PACK_SIZE:
                if LOG:
                    ctcplogging.info(str(self.client_address)+": "+"应接收数据{},已经接收{}\t".format(PACK_SIZE,rcv_len))
                sock_data = conn.recv(PACK_SIZE-rcv_len)
                if not sock_data:
                    err_log = '应接收{}个字节，实际没有收到数据,退出'.format(PACK_SIZE)
                    err_log += "服务器断开连接"
                    conn.close()
                    ctcplogging.error(str(self.client_address)+": "+ err_log)
                    return False
                recv_data += sock_data
                rcv_len=len(recv_data)
                if LOG:
                    ctcplogging.info(str(self.client_address)+": "+'已经接收{}, 继续接收数据\t'.format(rcv_len))
                    if rcv_len == PACK_SIZE:
                        ctcplogging.info(str(self.client_address)+": "+"数据接收完毕")        
        except Exception as e:        
            err_log = "接收数据，发生异常: " + str(e)
            err_log += "服务器断开连接"
            conn.close()
            ctcplogging.error(str(self.client_address)+": "+ err_log)
            return False
        return recv_data    
    
    def recv_allbytes(self, packlen):
        PACK_SIZE = 5000 # socket 一次读取大小        
        if packlen <= PACK_SIZE:
            recv_alldata = self.recv_bytes(packlen)
            return recv_alldata
        else:    
            recv_alldata = b''    
            while packlen > PACK_SIZE:
                tmp_data = self.recv_bytes(PACK_SIZE)
                if tmp_data == False:
                    return False
                recv_alldata += tmp_data
                packlen = packlen - PACK_SIZE
            tmp_data = self.recv_bytes(packlen)
            if tmp_data == False:
                return False
            recv_alldata += tmp_data
            return recv_alldata
    
    def print_cdata(self, cdata, msgtype):
        if LOG:
            msg = msgtype + '\n'
            #msg += "数据内容: {}".format(cdata) + '\n'
            #msg += "二进制是："+ ''.join( [ "%02X " % x for x in cdata ] ).strip()# bytes to hex_string的转换
            ctcplogging.info(str(self.client_address)+": "+ msg)
            
    def process_AUTH_INIT(self, realmbytes, noncebytes, opaquebytes):
        conn = self.request        
        back_data  = self.pack_start_code + bytes.fromhex(' 00 00 00 92 ')# self.pack_start_code + pack_length
        back_data += bytes.fromhex(' 18 00 ') # self.body_flag=1 self.msg_type='10' ,self.msg_code=0
        back_data += self.request_id + self.result_code
        back_data += bytes.fromhex(' 00 00 00 82 ') # self.json_length
        back_data += bytes.fromhex(' 7b 0a 20 20  20 20 22 72 \
                                     65 61 6c 6d 22 3a 20 22 ')  #{.    "realm": "  16个字符
        back_data += realmbytes # 15个字符
        back_data += bytes.fromhex(' 22 2c 0a 20 20 20 20 22 6e \
                                     6f 6e 63 65 22 3a 20 22 ')  # ",.    "nonce": " 15个字符
        back_data += noncebytes # 2个字符
        back_data += bytes.fromhex(' 22 2c 0a 20 20 20 20 22 6f \
                                     70 61 71 75 65 22 3a 20 22')   # ",.    "opaque": " 16个字符
        back_data += opaquebytes # 16个字符
        back_data += bytes.fromhex(' 22 2c 0a 20 20 20 20 22 \
                                     71 6f 70 22 3a 20 22 ')      # ",.    "qop": "  15个字符
        back_data += bytes.fromhex(' 61 75 74 68 ') # auth 4个字符
        back_data += bytes.fromhex(' 22 2c 0a 20 20 20 20 22 \
                                     61 6c 67 6f 72 69 74 68 6d 22 3a 20 22 ')#  ",.    "algor ithm": " 21个字符
        back_data += bytes.fromhex(' 4d 44 35 22 0a 7d ') # MD5".} 6个字符
        back_data += bytes.fromhex(' 00 00 00 00 ') # binary_datas_length=0表示无json数据  4个字符
        
        ret = self.response_msgtype(back_data, 'AUTH_INIT')
        if ret == False:
            return False
        conn.settimeout(5)  # 设置延时2s ，注册认证成功后增加延时
        return True
    
    def process_AUTH_REQUEST(self):    
        conn = self.request        
        # 注册认证
        if self.body_flag == 1:
            json_dict=json.loads(self.json_str)
            if LOG:
                ctcplogging.info(str(self.client_address)+": "+'{}'.format(json_dict))
            username=json_dict["username"] # "00000000001310000052"
            realm=json_dict["realm"] # "000006123456789"
            uri=json_dict["uri"] # "cpss://10.90.26.233:9999"
            nonce=json_dict["nonce"] # '4c'
            nc=json_dict["nc"] # '000001'
            cnonce=json_dict["cnonce"] # "c5ede8c9e84753aede4e860b36c0ae8e"
            qop=json_dict["qop"] # "auth"
            password="12345678"
            A1 = username+":"+realm+":"+password
            A2 = "REGISTER_CPSS:"+uri

            m2 = hashlib.md5()
            m2.update(A1.encode('utf-8'))
            HA1 = m2.hexdigest()

            m2 = hashlib.md5()
            m2.update(A2.encode('utf-8'))
            HA2 = m2.hexdigest()

            response = HA1+":" +nonce+":" +nc+ ":" +cnonce+ ":"+qop+ ":"+ HA2

            m2 = hashlib.md5()
            m2.update(response.encode('utf-8'))
            response_md5 = m2.hexdigest()
            response=json_dict["response"]
            if response_md5 == response:
                ctcplogging.info(str(self.client_address)+": "+"认证成功")
                self.result_code = bytes.fromhex("00 00 00 00")
            else:
                ctcplogging.info(str(self.client_address)+": "+"认证失败")
                self.result_code = bytes.fromhex("00 00 00 01")
        else:
            err_log = "body_flag != 1 " +" 服务器断开连接\n"          
            conn.close()                  
            ctcplogging.error(str(self.client_address)+": "+ err_log)
            return False

        back_data  = self.pack_start_code + bytes.fromhex('00 00 00 56  ') # self.pack_start_code+pack_length
        back_data += bytes.fromhex('18 01')# self.body_flag=1 self.msg_type='10' ,self.msg_code=1
        back_data += self.request_id + self.result_code
        back_data += bytes.fromhex('00 00 00 46') # self.json_length
        back_data += bytes.fromhex('7b 0a 20 20 20 20 22 53 79 \
                                    73 74 65 6d 54 69 6d 65 22 3a 20  22 ')
        SystemTime = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
        SystemTime = SystemTime.encode('utf8') # systemtime
        back_data += SystemTime + bytes.fromhex('  22 2c 0a 20 \
              20 20 20 22 4b 65 65 70  41 6c 69 76 65 50 65 72  \
              69 6f 64 22 3a 20 31 30  0a 7d ')# 固定值    ",. "Keep AlivePeriod":10 .}
        back_data += bytes.fromhex('00 00 00 00') # binary_datas_length=0表示无json数据

        if self.result_code == bytes.fromhex("00 00 00 00"): # 注册认证成功，socket延时设置
            conn.settimeout(15)
         
        ret = self.response_msgtype(back_data, 'AUTH_REQUEST')
        if ret == False:
            return False        
        if self.result_code == bytes.fromhex("00 00 00 00"): # 认证成功                               
            if self.client_address not in registerIPs:
                registerIPs.append(self.client_address)
                ctcplogging.info(str(self.client_address)+": "+ "已注册IP:{}".format(registerIPs))   
                ip_registerTime[self.client_address] = datetime.datetime.now()
        return True
    
    def process_KEEP_ALIVE(self):
        conn = self.request
        # hex_string to bytes的转换,通过socket发出
        back_data = self.pack_start_code + bytes.fromhex(' 00 00 00 08') # self.pack_start_code+pack_length
        back_data += bytes.fromhex('08 02 ') # self.body_flag=0 self.msg_type='10' ,self.msg_code=2        
        back_data += self.request_id + self.result_code   
        
        ret = self.response_msgtype(back_data, 'KEEP_ALIVE')
        if ret == False:
            return False            
        return True
        
    def process_REPORT_FACE(self, pack_length):
        conn = self.request
        client_alldata = self.client_headdata + self.client_data # 8字节数据 + data
        if noresponse==1:                                            
            back_data = self.pack_start_code + bytes.fromhex("00 00 00 08") # self.pack_start_code+pack_length
            back_data += bytes.fromhex("08 08 ")     # self.body_flag=0 self.msg_type='10' ,self.msg_code=8
            back_data += self.request_id + self.result_code            
            ret = self.response_msgtype(back_data, 'REPORT_FACE')
            if ret == False:
                return False  
            self.picPacksum += 1                                            
            resultPicNum[self.client_address]= [self.picPacksum]                               
        else:        
            # ------------写bin文件和jpg--------------
            jpg_filename = []
            if self.body_flag==1:
                json_dict=json.loads(self.json_str)
                #ctcplogging.info(str(self.client_address)+": "+"转成字典{}".format(json_dict))
                #ctcplogging.info(str(self.client_address)+": "+json_dict["FaceSetId"])
                FaceSetId = json_dict["FaceSetId"]   
                Id = FaceSetId
                ShotTime = json_dict["ShotTime"]
                #ShotTimeMicroSecond = ShotTime.split('.')[1]
                ShotTimeMicroSecond = ShotTime.replace("-","")
                ShotTimeMicroSecond = ShotTimeMicroSecond.replace(" ","")
                ShotTimeMicroSecond = ShotTimeMicroSecond.replace(":","")
                TimeMicroSecond = ShotTimeMicroSecond.replace(".","")
                bin_filename = self.jpg_dir + "/" + 'face_' + Id + "_" + TimeMicroSecond + '.bin'
                # 大图
                big=json_dict["PicRef"]   
                filename = self.jpg_dir + "/" + 'face_' + Id + "_" + TimeMicroSecond + "_" + str(big) + ".jpg"
                jpg_filename.append(filename)
                self.Bigpicsum += 1
                # small_pic
                for i in range(len(json_dict["Faces"])):
                    small = (json_dict["Faces"][i])["PicRef"]
                    jpg_filename.append(self.jpg_dir + "/" + 'face_' + Id + "_"
                                        + TimeMicroSecond + "_" + str(small) + ".jpg")        
                    self.Smallpicsum += 1                          
            else:
                err_log = "body_flag != 1 " +" 服务器断开连接\n"          
                conn.close()                  
                ctcplogging.error(str(self.client_address)+": "+ err_log)
                return False
            if save_cpssbin:     
                ctcplogging.info(str(self.client_address)+": "+"bin文件{}".format(bin_filename))
                self.savedata(bin_filename, client_alldata)
            jpg_Position = 20 + self.json_length
            for i in range(len(jpg_filename)):
                if os.path.isfile(jpg_filename[i]):#如果图片已经存在，说明facesetid重复
                    err_log = "当前目录有重名图片:{}".format(jpg_filename[i])
                    ctcplogging.warning(str(self.client_address)+": "+ err_log)
                    self.repeatpicsum += 1
                    #ctcplogging.info(str(self.client_address)+": "+"服务器断开连接\n")
                    #conn.close()
                    #jpg_filename[i] += '_re.png'  
                    #return    False                                   

                jpg_length = int.from_bytes(client_alldata[jpg_Position: jpg_Position + 4], byteorder='big', signed=False)
                jpg_head = jpg_Position + 4
                if jpg_length > 0:
                    if jpg_head + jpg_length <= pack_length+8:
                        jpg_head_data = client_alldata[jpg_head: jpg_head + 2]
                        jpg_end_data = client_alldata[jpg_head + jpg_length - 2: jpg_head + jpg_length]
                        if jpg_head_data != b'\xff\xd8' or jpg_end_data != b'\xff\xd9':
                            self.errpicsum += 1 # 错误图片数量
                            self.savedata(jpg_filename[i], client_alldata[jpg_head:jpg_head + jpg_length])
                            self.savedata(bin_filename, client_alldata)              
                            jpg_head_hex = ''.join(["%02X " % x for x in jpg_head_data]).strip()
                            jpg_end_hex = ''.join(["%02X " % x for x in jpg_end_data]).strip()
                            err_log = "注意:图片格式错误，{},{}".format(jpg_filename[i],bin_filename)
                            err_log += " 开始2字节: {},结束2字节: {}".format(jpg_filename[i], jpg_head_hex, jpg_end_hex)
                            ctcplogging.error(str(self.client_address) + ": " + err_log)
                            if check_jpg == 1:                   
                                self.result_code = bytes.fromhex("00 00 00 01")        # 错误码 1 
                        else:
                            if save_jpg:                                                        
                                ctcplogging.info(str(self.client_address) + ": " + "jpg文件{},图片大小{}".format(jpg_filename[i], jpg_length))                                                      
                                self.savedata(jpg_filename[i], client_alldata[jpg_head:jpg_head + jpg_length])     
                        jpg_Position += jpg_length + 4
                    else:
                        self.savedata(jpg_filename[i], client_alldata[jpg_head:jpg_head + jpg_length])
                        self.savedata(bin_filename, client_alldata)        
                        err_log = "jpg_head + jpg_length > pack_length+8" +" 服务器断开连接\n"
                        conn.close()
                        ctcplogging.error(str(self.client_address) + ": " + "注意:图片长度有误,{},{}".format(jpg_filename[i],bin_filename))
                        ctcplogging.error(str(self.client_address) + ": " + "jpg_head:{}，jpg_length:{}，pack_length:{}".format(jpg_head,jpg_length,pack_length))
                        ctcplogging.error(str(self.client_address)+": "+ err_log)
                        return False
                else:
                    self.zeropicsum +=1 # 图片大小为0的数量
                    ctcplogging.warning(
                        str(self.client_address) + ": " + "注意:图片{} 大小是{}".format(
                            jpg_filename[i], jpg_length)) 
                    self.savedata(jpg_filename[i], client_alldata[jpg_head:jpg_head + jpg_length])
                    jpg_Position += jpg_length + 4
            back_data = self.pack_start_code + bytes.fromhex("00 00 00 08") # self.pack_start_code+pack_length
            back_data += bytes.fromhex("08 08 ")     # self.body_flag=0 self.msg_type='10' ,self.msg_code=8
            back_data += self.request_id + self.result_code   
                     
            ret = self.response_msgtype(back_data, 'REPORT_FACE')
            if ret == False:
                return False                              
            #统计图片数量
            self.picPacksum += 1     
            resultPicNum[self.client_address]= [self.picPacksum,self.Bigpicsum,self.Smallpicsum,\
                                        self.BigMotorpicsum,self.SmallMortopicsum ,\
                                        self.errpicsum,self.zeropicsum, self.repeatpicsum]
        
        results = resultPicNum[self.client_address]
        # 图片数据包总数,大图,小图,错误图片数量,图片大小为0的数量,一个数据包中最多的图片数量,时间
        # 1秒图片最多数量，时间，[时间，图片数量]
        # 1秒最多包数量，时间，[时间，包数量]                                            
        if len(results)==1:         
            msg = " {}:\n\t数据包数量:{}".format(\
                            self.client_address,results[0])         
        else:        
            msg = " {}:\n\t数据包数量:{}".format(self.client_address,results[0])
            msg += "\n\tface大图:{},face小图:{}".format(results[1],results[2])
            msg += "\n\tMotor大图:{},Motor小图:{}".format(results[3],results[4])             
            msg += "\n\t[其中错误图片:{},大小为0的图片:{},重名图片:{}]".format(results[5],results[6],results[7])                         
        ctcplogging.info(str(self.client_address)+": "+ msg)            
        return True
    
    def process_REPORT_VEHICLE(self, pack_length):
        conn = self.request
        client_alldata = self.client_headdata + self.client_data # 8字节数据 + data        
        if noresponse==1:                                            
            back_data = self.pack_start_code + bytes.fromhex("00 00 00 08") # self.pack_start_code+pack_length
            back_data += bytes.fromhex("08 03 ")     # self.body_flag=0 self.msg_type='10' ,self.msg_code=3
            back_data += self.request_id + self.result_code                      
            ret = self.response_msgtype(back_data, 'REPORT_VEHICLE')
            if ret == False:
                return False 
            self.picPacksum += 1                                           
            resultPicNum[self.client_address]= [self.picPacksum]                              
        else:        
            # ------------写bin文件和jpg--------------
            jpg_filename = []
            if self.body_flag==1:
                json_dict=json.loads(self.json_str)
                #ctcplogging.info(str(self.client_address)+": "+"转成字典{}".format(json_dict))
                #ctcplogging.info(str(self.client_address)+": "+json_dict["FaceSetId"])
            
                MotorVehicleID = json_dict["MotorVehicleID"]  
                Id = MotorVehicleID 
                PassTime = json_dict["PassTime"]
                PassTimeMicroSecond = PassTime.replace("-","")
                PassTimeMicroSecond = PassTimeMicroSecond.replace(" ","")
                PassTimeMicroSecond = PassTimeMicroSecond.replace(":","")
                TimeMicroSecond = PassTimeMicroSecond.replace(".","")
                bin_filename = self.jpg_dir + "/" + 'motor_' + Id + "_" + TimeMicroSecond + '.bin'                
                for i in range(len(json_dict["Pictures"])):
                    small = (json_dict["Pictures"][i])["Ref"]     
                    ShotTime = (json_dict["Pictures"][i])["ShotTime"]
                    ShotTimeMicroSecond = ShotTime.replace("-","")
                    ShotTimeMicroSecond = ShotTimeMicroSecond.replace(" ","")
                    ShotTimeMicroSecond = ShotTimeMicroSecond.replace(":","")
                    ShotTimeMicroSecond = ShotTimeMicroSecond.replace(".","")
                    pictype = (json_dict["Pictures"][i]).get("Type")
                    if pictype == 2:
                        filename = self.jpg_dir + "/" + 'motor_' + Id + "_"\
                                        + TimeMicroSecond + "_" + ShotTimeMicroSecond + "_" \
                                        + str(small) + "_Big.jpg"
                    else:
                        filename = self.jpg_dir + "/" + 'motor_' + Id + "_"\
                                        + TimeMicroSecond + "_" + ShotTimeMicroSecond + "_" \
                                        + str(small) + "_Small.jpg"
                        
                    jpg_filename.append(filename)        
                    if pictype == 2:
                        self.BigMotorpicsum +=1
                    else:
                        self.SmallMortopicsum += 1                           
            else:
                err_log = "body_flag != 1 " +" 服务器断开连接\n"          
                conn.close()                  
                ctcplogging.error(str(self.client_address)+": "+ err_log)
                return False
            if save_cpssbin:     
                ctcplogging.info(str(self.client_address)+": "+"bin文件{}".format(bin_filename))
                self.savedata(bin_filename, client_alldata)   
            jpg_Position = 20 + self.json_length
            for i in range(len(jpg_filename)):
                if os.path.isfile(jpg_filename[i]):#如果图片已经存在，说明facesetid重复
                    err_log = "当前目录有重名图片:{}".format(jpg_filename[i])
                    ctcplogging.warning(str(self.client_address)+": "+ err_log)
                    self.repeatpicsum += 1
                    #ctcplogging.info(str(self.client_address)+": "+"服务器断开连接\n")
                    #conn.close()
                    #jpg_filename[i] += '_re.png'  
                    #return                                      

                jpg_length = int.from_bytes(client_alldata[jpg_Position: jpg_Position + 4], byteorder='big', signed=False)
                jpg_head = jpg_Position + 4
                if jpg_length > 0:
                    if jpg_head + jpg_length <= pack_length+8:
                        jpg_head_data = client_alldata[jpg_head: jpg_head + 2]
                        jpg_end_data = client_alldata[jpg_head + jpg_length - 2: jpg_head + jpg_length]
                        if jpg_head_data != b'\xff\xd8' or jpg_end_data != b'\xff\xd9':
                            self.errpicsum += 1 # 错误图片数量
                            self.savedata(jpg_filename[i], client_alldata[jpg_head:jpg_head + jpg_length])
                            self.savedata(bin_filename, client_alldata) 
                            jpg_head_hex = ''.join(["%02X " % x for x in jpg_head_data]).strip()
                            jpg_end_hex = ''.join(["%02X " % x for x in jpg_end_data]).strip()
                            err_log = "注意:图片格式错误，{},{}".format(jpg_filename[i],bin_filename)
                            err_log += " 开始2字节: {},结束2字节: {}".format(jpg_filename[i], jpg_head_hex, jpg_end_hex)
                            ctcplogging.error(str(self.client_address) + ": " + err_log)
                            if check_jpg == 1:                   
                                self.result_code = bytes.fromhex("00 00 00 01")        # 错误码 1 
                        else:
                            if save_jpg:                                                        
                                ctcplogging.info(str(self.client_address) + ": " + "jpg文件{},图片大小{}".format(jpg_filename[i], jpg_length))                                                      
                                self.savedata(jpg_filename[i], client_alldata[jpg_head:jpg_head + jpg_length])
                        jpg_Position += jpg_length + 4
                    else:
                        self.savedata(jpg_filename[i], client_alldata[jpg_head:jpg_head + jpg_length])
                        self.savedata(bin_filename, client_alldata)    
                        err_log = "jpg_head + jpg_length > pack_length+8" +" 服务器断开连接\n"
                        conn.close()
                        ctcplogging.error(str(self.client_address) + ": " + "注意:图片长度有误,{},{}".format(jpg_filename[i],bin_filename))
                        ctcplogging.error(str(self.client_address) + ": " + "jpg_head:{}，jpg_length:{}，pack_length:{}".format(jpg_head,jpg_length,pack_length))
                        ctcplogging.error(str(self.client_address)+": "+ err_log)
                        return False
                else:
                    self.zeropicsum +=1 # 图片大小为0的数量
                    ctcplogging.warning(
                        str(self.client_address) + ": " + "注意:图片{} 大小是{}".format(
                            jpg_filename[i], jpg_length))
                    self.savedata(jpg_filename[i], client_alldata[jpg_head:jpg_head + jpg_length])                     
                    jpg_Position += jpg_length + 4
            
            back_data = self.pack_start_code + bytes.fromhex("00 00 00 08") # self.pack_start_code+pack_length
            back_data += bytes.fromhex("08 03 ")     # self.body_flag=0 self.msg_type='10' ,self.msg_code=3
            back_data += self.request_id + self.result_code   
            
            ret = self.response_msgtype(back_data, 'REPORT_VEHICLE')
            if ret == False:
                return False                                 
            #统计图片数量
            self.picPacksum += 1     
            resultPicNum[self.client_address]= [self.picPacksum,self.Bigpicsum,self.Smallpicsum,\
                                        self.BigMotorpicsum,self.SmallMortopicsum ,\
                                        self.errpicsum,self.zeropicsum, self.repeatpicsum]
        
        results = resultPicNum[self.client_address]
        # 图片数据包总数,大图,小图,错误图片数量,图片大小为0的数量,一个数据包中最多的图片数量,时间
        # 1秒图片最多数量，时间，[时间，图片数量]
        # 1秒最多包数量，时间，[时间，包数量]                                            
        if len(results)==1:         
            msg = " {}:\n\t数据包数量:{}".format(\
                            self.client_address,results[0])         
        else:        
            msg = " {}:\n\t数据包数量:{}".format(self.client_address,results[0])
            msg += "\n\tface大图:{},face小图:{}".format(results[1],results[2])
            msg += "\n\tMotor大图:{},Motor小图:{}".format(results[3],results[4])             
            msg += "\n\t[其中错误图片:{},大小为0的图片:{},重名图片:{}]".format(results[5],results[6],results[7])                       
        ctcplogging.info(str(self.client_address)+": "+ msg)                    
        return True        
    
    def savedata(self, filename, data):    
        # 保存 二进制 文件
        try:
            with open(filename, 'wb') as f:
                f.write(data)
        except Exception as e:
            ctcplogging.error(str(self.client_address)+": " + '写{},出现异常:'.format(filename) + str(e))
            
    def handle(self):        
        ctcplogging.info(str(self.client_address)+": "+'服务端启动...')   
        self.getThreads('接收消息')
        self.init_params()
        
        conn = self.request
        conn.settimeout(5) # 设置延时2s ，注册认证成功后改成20s
        
        #self.msg_type
        MT_NOTIFY = 0 #	'00'	通知消息，无需应答
        MT_REQ = 1    #	'01'	需要应答的请求消息
        MT_RES = 2    #	'10'	应答消息（与请求消息的request_id相同）
        #self.msg_code
        AUTH_INIT = 0 #	R	AuthInitType	AuthChallengeType	前端/客户端初始连接CPSS时发送
        AUTH_REQUEST =  1 #	R	AuthRequestType	AuthAcceptType*	拒绝时无body或带ErrorDescType
        KEEP_ALIVE =	2 #	R	无body	无body	心跳
        REPORT_VEHICLE =3 #	R	MotionVehicleType	无body	上报过车记录
        REPORT_FACE =	8 #	R	FaceSetType	无body	上报人脸记录（一张全景加若干人脸）

        # ------------有新连接时，产生随机数-------------------            
        realmbytes, noncebytes, opaquebytes = self.create_random()
        # -------------随机数------------------------------
        try:         
            self.jpg_dir = './ctcp/pics/' + self.client_address[0]
            if not os.path.isdir(self.jpg_dir ): 
                try:
                    os.makedirs(self.jpg_dir)
                    ctcplogging.info(str(self.client_address) +": "+"创建目录：" + self.jpg_dir)
                except Exception as e:                
                    ctcplogging.warning(str(self.client_address) +": "+"创建目录：" + self.jpg_dir)
            while True:  
                if not _running: # _running=False 退出
                    conn.close()
                    return
                # 1, 读8字节数据 
                self.client_headdata = self.read_8bits()
                if self.client_headdata == False:
                    return
                self.usetime = time.time()
                ctcplogging.info(str(self.client_address)+": "+ "-----------START---------------------------")
                ctcplogging.info(str(self.client_address)+": "+ "收到8字节数据:{},二进制{}".
                             format(self.client_headdata,''.join(["%02X " % x for x in self.client_headdata]).strip()))
                    
                pack_length = int.from_bytes(self.client_headdata[4:8],byteorder='big',signed=False)
                if LOG:
                    #pack_length=struct.unpack('>i', self.client_headdata[-4:])[0]
                    ctcplogging.info(str(self.client_address)+": "+"pack_length = {}".format(pack_length))
                # 2, 读数据
                self.client_data = self.recv_allbytes(pack_length) 
                if self.client_data == False:
                    return
                self.print_cdata(self.client_data, '')
                # 3，解析
                # self.client_headdata 共8个字节，self.client_data是数据
                Hi = int.from_bytes(self.client_data[0:1],byteorder='big',signed=False) #转成int类型
                Lo = int.from_bytes(self.client_data[1:2],byteorder='big',signed=False) #转成int类型

                self.reserved = (Hi & 0xe0) >> 5         #   3bit
                self.body_flag = (Hi & 0x10) >> 4        #	1
                self.msg_type = (Hi & 0x0c) >> 2         #	2
                self.msg_code = ((Hi & 0x03) << 8 )| Lo  #	10
                self.request_id = self.client_data[2:4]
                request_id_int = int.from_bytes(self.client_data[2:4],byteorder='big',signed=False) #转成int类型
                ctcplogging.info(str(self.client_address)+": "+"self.request_id:{}".format(request_id_int))

                self.have_resultcode = 0
                self.json_length = 0
                self.json_str = ''
                self.binary_data_length = 0
                if self.msg_type == MT_RES:
                    if self.body_flag == 1:
                        self.have_resultcode = 4   # 回复消息有resultcode，4字节
                if self.body_flag == 1:
                    self.json_length = int.from_bytes(self.client_data[4+self.have_resultcode:8+self.have_resultcode],byteorder='big',signed=False)
                    self.json_str = self.client_data[8+self.have_resultcode:8+self.have_resultcode+self.json_length]\
                        .decode('utf-8',"ignore")
                    if LOG:
                        ctcplogging.info(str(self.client_address)+": "+"self.json_str={}".format(self.json_str))
                    self.binary_data_length = int.from_bytes(self.client_data[8+self.have_resultcode+self.json_length:12+self.have_resultcode+self.json_length]\
                                                            ,byteorder='big',signed=False)
                
                # 4， 根据msg_type判断数据类型
                if self.msg_type == MT_NOTIFY:
                    ctcplogging.info(str(self.client_address)+": "+"MT_NOTIFY")
                    ctcplogging.info(str(self.client_address)+": "+ "收到数据:{},二进制{}".
                         format(self.client_data,''.join(["%02X " % x for x in self.client_data]).strip()))
                elif self.msg_type == MT_REQ:
                    ctcplogging.info(str(self.client_address)+": "+"MT_REQ")
                    if self.msg_code == AUTH_INIT:
                        ret = self.process_AUTH_INIT(realmbytes, noncebytes, opaquebytes)
                        if ret == True:
                            pass
                        else:
                            return  
                    elif self.msg_code == AUTH_REQUEST:  
                        ret = self.process_AUTH_REQUEST()
                        if ret == True:
                            pass
                        else:
                            return        
                    elif self.msg_code == KEEP_ALIVE:
                        ctcplogging.info(str(self.client_address)+": "+"***KEEP_ALIVE***")
                        if self.client_address not in registerIPs:
                            ctcplogging.error(str(self.client_address)+": "+"没有注册,服务器断开连接\n")
                            conn.close()
                            return   
                        self.result_code = output_resultcode(result_code_err)        
                        ret = self.process_KEEP_ALIVE()   
                        if ret == True:
                            pass
                        else:
                            return                      
                    elif self.msg_code == REPORT_FACE:   
                        ctcplogging.info(str(self.client_address)+": "+"***REPORT_FACE***")    
                        if self.client_address not in registerIPs:
                            ctcplogging.error(str(self.client_address)+": "+"没有注册,服务器断开连接\n")
                            conn.close()
                            return          
                        self.result_code = output_resultcode(result_code_err)
                        ret = self.process_REPORT_FACE(pack_length)
                        if ret == True:
                            pass
                        else:
                            return                        
                    elif self.msg_code == REPORT_VEHICLE:
                        ctcplogging.info(str(self.client_address)+": "+"***REPORT_VEHICLE***")
                        if self.client_address not in registerIPs:
                            ctcplogging.error(str(self.client_address)+": "+"没有注册,服务器断开连接\n")
                            conn.close()
                            return          
                        self.result_code = output_resultcode(result_code_err)
                        ret = self.process_REPORT_VEHICLE(pack_length)
                        if ret == True:
                            pass
                        else:
                            return  
                    else:
                        err_log = "***不支持msg_code={}***".format(self.msg_code)                    
                        ctcplogging.error(str(self.client_address)+": "+ err_log)                                                                
                        if self.client_address not in registerIPs:
                            ctcplogging.error(str(self.client_address)+": "+"没有注册,服务器断开连接\n")
                            conn.close()
                            return    
                elif self.msg_type == MT_RES:
                    ctcplogging.info(str(self.client_address)+": "+"MT_RES应答消息")
                else:
                    err_log = "Reserve，退出"                            
                    ctcplogging.error(str(self.client_address)+": "+ err_log+",服务器断开连接\n")
                    conn.close()
                    return    
                if LOG:
                    msg = '所有注册的前端，图片统计结果:'                
                    for k, results in resultPicNum.items():
                        results = resultPicNum[k]
                        # 图片数据包总数,大图,小图,错误图片数量,图片大小为0的数量,一个数据包中最多的图片数量,时间
                        # 1秒图片最多数量，时间，[时间，图片数量]
                        # 1秒最多包数量，时间，[时间，包数量]  
                        msg += "\n\t{}:".format(k)                          
                        registerTime = ip_registerTime.get(k)
                        if registerTime:
                            msg += "\n\t注册时间:{}".format(registerTime)                                        
                        if len(results)==1:         
                            msg += "\n\t数据包数量:{}".format(results[0])   
                            msg += "\n\t"     
                        elif len(results)>1:        
                            msg += "\n\t数据包数量:{}".format(results[0])
                            msg += "\n\tface大图:{},face小图:{}".format(results[1],results[2])
                            msg += ",Motor大图:{},Motor小图:{}".format(results[3],results[4])             
                            msg += "\n\t[其中错误图片:{},大小为0的图片:{},重名图片:{}]".format(results[5],results[6],results[7])                         
                            #msg += "\n\t" +'#'*50
                    ctcplogging.info(str(self.client_address)+": "+ msg)           
                    
                ctcplogging.info(str(self.client_address)+": "+"------------END--------------------------\n")            
        except Exception as e:
            ctcplogging.error(str(self.client_address)+": " + ' 出现异常:' + str(e))
        conn.close()
        ctcplogging.error(str(self.client_address)+": "+"服务器主动断开连接\n")

    def create_random(self):
        """ 功能：产生随机数
            :return:3个随机数
        """
        # ------------有新连接时，产生随机数-------------------
        number = ['30', '31', '32', '33', '34', '35', '36', '37', '38', '39']
        total = ['30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '61', '62', '63', '64', '65', '66']

        realm = 15  # 取值范围0-9，共15个
        realmstr = ''
        for i in range(realm):
            realmlist = random.sample(number, 1)
            realmstr += realmlist[0]
        realmbytes = bytes.fromhex(realmstr)

        nonce = 2  # 取值范围0-f，共2个
        noncestr = ''
        for i in range(nonce):
            noncelist = random.sample(total, 1)
            noncestr += noncelist[0]
        noncebytes = bytes.fromhex(noncestr)

        opaque = 16  # 取值范围0-f，共16个
        opaquestr = ''
        for i in range(opaque):
            opaquelist = random.sample(total, 1)
            opaquestr += opaquelist[0]
        opaquebytes = bytes.fromhex(opaquestr)
        # -------------随机数------------------------------
        return ([realmbytes, noncebytes, opaquebytes])
    
    def response_msgtype(self, back_data, msgtype):
        conn = self.request
        try:
            if conn.sendall(back_data) == None:
                ctcplogging.info(str(self.client_address)+": " + msgtype + ' 返回响应消息成功')
            else:
                err_log = msgtype + "发送数据失败" + "服务器断开连接"   
                conn.close()                            
                ctcplogging.error(str(self.client_address)+": "+ err_log)
                return False            
        except Exception as e:
            err_log = msgtype + "发送数据失败, 异常: " + str(e) + "服务器断开连接"   
            conn.close()                            
            ctcplogging.error(str(self.client_address)+": "+ err_log)
            return False
        self.print_cdata(back_data, msgtype)       
        ctcplogging.info(str(self.client_address)+": "+ msgtype + " 返回消息时长:" + "%.06f"%(time.time()-self.usetime) + 's')
        return True

def runOnce(HOST,PORT):
    """
        功能：调用后，一次只处理一个连接请求
    :param IP:
    :param PORT:
    :return:
    """
    mkdir()
    init_loggingSetCTCP()
    print('ip地址,端口：', (HOST, PORT))
    server =socketserver.TCPServer((HOST, PORT),echorequestserver)  # 使用处理单连接的TCPServer
    server.handle_request()
    
    
class Ctcp():
    def __init__(self):
        self.mkdir()
        self.introMsg = 'CTCP服务器信息\n'
        self.introMsg+="***********************************" + '\n'
        self.introMsg+="server version is %s"%VERSION + '\n'
        self.introMsg+="password is 12345678" + '\n'
        self.introMsg+='"Keep AlivePeriod":10s' + '\n'
        self.introMsg+="***********************************" + '\n'
        
        self.introMsg+="-------------------------------------------------" + '\n'
        self.introMsg+="图片命名：faceSetId_ShotTime_数字.jpg" + '\n'
        self.introMsg+="-------------------------------------------------"   + '\n' 
        print(self.introMsg)
        # 说明信息
        self.infomsg = '说明信息:\n'
        
    def mkdir(self):
        if not os.path.isdir("ctcp/log"):
            os.makedirs("ctcp/log")
        if not os.path.isdir("ctcp/pics"):
            os.makedirs("ctcp/pics")
        
    def run(self, ip, port):
        global _running
        global server
        _running = True
        if threadNumsLock.locked():
            threadNumsLock.release()    
        init_loggingSetCTCP()   
        ctcplogging.info(self.introMsg)
        try:  
            server = socketserver.ThreadingTCPServer((ip, port), echorequestserver)  # 使用多线程TCP服务器
            allThreads.clear()
            registerIPs.clear()
            resultPicNum.clear()
            ip_registerTime.clear()
            server_runtime[0] = time.time()
            ctcplogging.info('删除所有设备的注册信息')  
            ctcplogging.info('CTCP服务器已启动，IP地址,端口:(%s , %d)'%(ip, port))  
            server.serve_forever()
        except Exception as e:
            ctcplogging.error('CTCP服务器启动失败，IP地址,端口:(%s , %d)'%(ip, port) + ", " + str(e))
            
    def stop(self):
        global _running
        if threadNumsLock.locked():
            threadNumsLock.release()
        _running = False
        if server == 0:
            pass 
        elif server.fileno()>0:
            server.shutdown()
            server.server_close() 
            ctcplogging.info('CTCP服务器已停止')
            
def fetch_device(deviceips):   
    msg =''
    if server_runtime[0] > 0:
        #msg += '服务器启动时间：%s\n'%time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(server_runtime[0]))
        msg += 'CTCP服务器启动时间：%s\n'%str(datetime.datetime.fromtimestamp(server_runtime[0]))
    else:
        msg += 'CTCP服务器未启动\n'
        return msg
    #msg += '当前时间: %s\n'%time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
    msg += '当前时间: %s'%str(datetime.datetime.now())    
    notregisterIp_list = []
    if len(deviceips.strip()) == 0:
        iplist = ip_registerTime.keys()
    else:
        iplist = []
        # get all ip
        ips = deviceips.split(';') 
        all_ip = [x.strip() for x in ips]
        # get register ip_list
        registerip_port_dictkey = ip_registerTime.keys()
        registerip_port_list = list(registerip_port_dictkey)
        registerip_list = [x for x, y in registerip_port_list]
        """
        a = [x for x in list1 if x in list2] #两个列表中都存在
        b = [y for y in (list1 + list2) if y not in a] #两个列表中的不同元素
        c = [x for x in list1 if x not in list2]  #在list1列表中而不在list2列表中
        d = [y for y in list2 if y not in list1]  #在list2列表中而不在list1列表中        
        """
        notregisterIp_list = [x for x in all_ip if x not in registerip_list]
        for ip in all_ip:
            for ip_port in registerip_port_list:
                if ip == ip_port[0]:
                    iplist.append(ip_port)
    for ip in notregisterIp_list:
        msg += "\n\t%s not register"%ip        
    for ip_port in iplist: 
        if ip_port:   
            registerTime = ip_registerTime.get(ip_port)
            if registerTime:
                msg += "\n\t{}".format(ip_port)
                msg += " 注册时间:{}".format(registerTime)                   
                results = resultPicNum.get(ip_port)
                if results == None:  # 没有注册
                    msg += "\n\t    not pics".format(ip_port)
                else:
                    # 图片数据包总数,大图,小图,错误图片数量,图片大小为0的数量,一个数据包中最多的图片数量,时间
                    # 1秒图片最多数量，时间，[时间，图片数量]
                    # 1秒最多包数量，时间，[时间，包数量]                                            
                    if len(results)==1:         
                        msg += "\n\t    数据包数量:{}".format(results[0])         
                    elif len(results)>1:        
                        msg += "\n\t    数据包数量:{}".format(results[0])
                        msg += "\n\t    face大图:{},face小图:{}".format(results[1],results[2])
                        msg += ",Motor大图:{},Motor小图:{}".format(results[3],results[4])             
                        msg += "\n\t    [其中错误图片:{},大小为0的图片:{},重名图片:{}]".format(results[5],results[6],results[7])                         
            else:
                msg += "\n\t{} not found registerTime".format(ip_port)     
                
    msg += '\n'
    ctcplogging.info(msg)  
    return msg
                
if __name__ == '__main__':
    ch = input("\n不解析直接回复，直接回车解析数据后回复：")
    if ch == '':
        noresponse = 0
        print("解析数据后回复")
    else:
        noresponse = 1
        print("不解析数据后回复") 
        
    ch = input("\n显示全部打印，直接回车打印信息较少：")
    if ch == '':
        LOG = 0
        print("打印信息较少")
    else:
        LOG = 1
        print("全部打印信息")    
    ch = input("\n是否保存cpss文件，直接回车不保存")
    if ch == "":
        save_cpssbin = 0
    else:
        save_cpssbin = 1
    ch = input("\n是否保存jpg文件，直接回车不保存")
    if ch == "":
        save_jpg = 0
    else:
        save_jpg = 1
    ch = input("\n是否判断图片格式（回车默认不检测）")
    if ch=="":
        check_jpg = 0
    else:
        check_jpg = 1      
    HOST = input('\n请输入cpss的IP(回车默认本机IP):')
    print("IP: ", HOST)
    PORT = input('\n请输入cpss的端口(回车默认9999)：')
    if PORT == '':
        PORT = 9999
    PORT = int(PORT)
    print("端口：",PORT)        
    ctcp = Ctcp()
    ctcp.run(HOST, PORT)
